#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "DRMVideoSink.h"
#include "SpdLog.h"

//该文件参考了gstreamer中kmssink.c
DRMVideoSink::DRMVideoSink()
{
	m_Fd = -1;
	m_Connector = NULL;
	m_Res = NULL;
	m_PlaneRes = NULL;
	m_ConnID = -1;
	m_CrtcID = -1;
	m_PlaneID = -1;
	m_CopySize = -1;
	m_BuffIndex = 0;
    for(int i = 0; i < DRM_BUFFER_COUNT; i++) {
    	m_pBufObj[i] = NULL;
    }
}

DRMVideoSink::~DRMVideoSink()
{
	freeFB();
	if(m_PlaneRes)
		drmModeFreePlaneResources(m_PlaneRes);
	if(m_Connector)
		drmModeFreeConnector(m_Connector);
	if(m_Res)
		drmModeFreeResources(m_Res);
	if(m_Fd >= 0)
		close(m_Fd);
	if(m_DumpFile)
		fclose(m_DumpFile);
}

int DRMVideoSink::init(int width, int height, int hor_stride, int ver_stride, AVPixelFormat format)
{
	LOGD("width:%d height:%d hor_stride:%d ver_stride:%d format:0x%x", width, height, hor_stride, ver_stride, format);

	m_HorStride = hor_stride;
	m_VerStride = ver_stride;
	m_Width = width;
	m_Height = height;
	m_AVPixFmt = format;
	bool needSetCrtc = false;

	if(!m_Width || !m_Height) {
        LOGE("width(%d) or height(%d) error", m_Width, m_Height);
        goto err;
	}

	if(!m_HorStride)
		m_HorStride = width;
	if(!m_VerStride)
		m_VerStride = height;

	// m_DRMFormat = getDRMFormat(m_AVPixFmt);
	m_DRMFormat = DRM_FORMAT_NV12; //mpp固定解码为nv12格式

	int ret;
	m_Fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
    if (m_Fd < 0) {
        LOGE("failed to open card0, %s", strerror(errno));
        goto err;
    }

	// 如果设置此属性， drmModeGetResources会返回所有的plane，不设置则会只返回Overlay plane，gstreamer中未设置此属性
	// ret = drmSetClientCap(m_Fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
    // if (ret) {
    //     LOGE("drmSetClientCap failed: %s (%d)", strerror(errno), errno);
    //     goto err;
    // }

	m_Res = drmModeGetResources(m_Fd);
    if (!m_Res) {
        LOGE("drmModeGetResources failed: %s (%d)", strerror(errno), errno);
        goto err;
    }

    if(!m_Res->count_crtcs || !m_Res->count_connectors) {
        LOGE("count_crtcs(%d) or count_connectors(%d) failed", m_Res->count_crtcs, m_Res->count_connectors);
        goto err;
    }

    //rk3328只有一个crtc(vop),一个connector
	m_CrtcID = m_Res->crtcs[0];
	m_ConnID = m_Res->connectors[0];
	if (m_CrtcID < 0 || m_ConnID < 0) {
	    LOGE("m_CrtcID(%d) or m_ConnID(%d) failed", m_CrtcID, m_ConnID);
	    goto err;
	}

	m_Crtc = drmModeGetCrtc (m_Fd, m_CrtcID);
	if (!m_Crtc) {
	    LOGE("drmModeGetCrtc failed: %s (%d)", strerror(errno), errno);
	    goto err;
	}
	//当前pipeline的分辨率
	m_WDisplay = m_Crtc->mode.hdisplay;
	m_HDisplay = m_Crtc->mode.vdisplay;

	//如果已经建立硬件显示pipeline则不需要再调用drmModeSetCrtc了，UI进程QT会设置
	needSetCrtc = m_Crtc->mode_valid ? false : true;
	LOGI("needSetCrtc(%d) m_WDisplay(%d) m_HDisplay(%d)", needSetCrtc, m_WDisplay, m_HDisplay);

	m_PlaneRes = drmModeGetPlaneResources(m_Fd);
    if (!m_PlaneRes) {
        LOGE("drmModeGetPlaneResources failed: %s (%d)", strerror(errno), errno);
        goto err;
    }

	m_PlaneID = findPlaneForCrtc();
    if (m_PlaneID < 0) {
        LOGE("findPlaneForCrtc failed: %d", m_PlaneID);
        goto err;
    }

    //显示时需要拷贝的内存大小
    //YUV420P/YUV420SP/NV12/NV21
	if(m_DRMFormat == DRM_FORMAT_NV12)
		m_CopySize = m_HorStride * m_VerStride * 3 / 2;

	//YUV422
	if(m_DRMFormat == DRM_FORMAT_NV16)
		m_CopySize = m_HorStride * m_VerStride * 2;


    for(int i = 0; i < DRM_BUFFER_COUNT; i++) {
    	m_pBufObj[i] = createFB();
	    if (!m_pBufObj[i]) {
	        LOGE("failed to create FB");
	        goto err;
	    }
    }

	if(needSetCrtc && (configureModeSetting() < 0)) {
		LOGE("configureModeSetting failed");
		goto err;
	}

    if (m_WDisplay <= 0 || m_HDisplay <= 0) {
        LOGE("failed to get m_WDisplay(%d) and m_HDisplay(%d)", m_WDisplay, m_HDisplay);
        goto err;
    }

	//如果没有设置显示位置，默认全屏
	m_DisplayX = 0;
	m_DisplayY = 0;
	m_DisplayW = m_WDisplay;
	m_DisplayH = m_HDisplay;

	m_Inited = true;

    LOGI("init ok, conn_id:%d crtc_id:%d plane_id:%d buf width:%d height:%d size:%d vaddr:%p fb_id:%d m_CopySize:%d", \
    	m_ConnID, m_CrtcID, m_PlaneID, m_pBufObj[0]->width, m_pBufObj[0]->height, m_pBufObj[0]->size, m_pBufObj[0]->vaddr, \
    	m_pBufObj[0]->fb_id, m_CopySize);

    return 0;

err:
	if(m_PlaneRes)
		drmModeFreePlaneResources(m_PlaneRes);
	if(m_Connector)
		drmModeFreeConnector(m_Connector);
	if(m_Crtc)
		drmModeFreeCrtc(m_Crtc);
	if(m_Res)
		drmModeFreeResources(m_Res);
	if(m_Fd >= 0)
		close(m_Fd);

	return -1;
}

bool DRMVideoSink::isInited()
{
	return m_Inited;
}

Buffer_Object *DRMVideoSink::createFB()
{
	struct drm_mode_create_dumb create = {};
 	struct drm_mode_map_dumb map = {};

 	Buffer_Object *buff_obj = (Buffer_Object *)malloc(sizeof(Buffer_Object));
	memset(buff_obj, 0, sizeof(Buffer_Object));
	buff_obj->fb_id = -1;

	create.width = m_HorStride;
	create.height = m_VerStride;
	create.bpp = 32;
	int ret = drmIoctl(m_Fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
	if (ret) {
	    LOGE("failed to create dumb buf, %s", strerror(errno));
	    goto err;
	}

	if(m_CopySize > create.size) {
		LOGE("buffer size error, %d > %d", m_CopySize, create.size);
		return -1;
	}

	buff_obj->pitch = create.pitch;
	buff_obj->size = create.size;
	buff_obj->handle = create.handle;
	buff_obj->width = m_HorStride;
	buff_obj->height = m_VerStride;

	uint32_t handles[4];
	//pitches[0]:Y分量每一行的步进像素 >= width  pitchs[1]:UV分量每一行的步进像素，根据format来解释
	uint32_t pitches[4];
	//offset[0]:Y分量相对于buffer起始位置的偏移 offset[1]:UV分量相对于buffer起始位置的偏移
	uint32_t offsets[4];
	//DRM需要下面的值来解释内存像素信息
	switch(m_DRMFormat) {
		case DRM_FORMAT_NV12:
		case DRM_FORMAT_NV16:
			handles[0] = buff_obj->handle;
			handles[1] = buff_obj->handle;
			pitches[0] = m_HorStride;
			pitches[1] = m_HorStride;
			offsets[0] = 0;
			offsets[1] = m_HorStride * m_VerStride; //uv数据偏移量
			break;
		default: 
			LOGE("Unsupport format:%x", m_DRMFormat);
	}

	LOGI("m_DRMFormat:0x%x", m_DRMFormat);
	ret = drmModeAddFB2(m_Fd, buff_obj->width, buff_obj->height, m_DRMFormat, handles, pitches, 
		offsets, &buff_obj->fb_id, 0);
	if (ret) {
	    LOGE("drmModeAddFB2 failed: %s (%d)", strerror(errno), errno);
	    goto err;
	}
	LOGI("buffer_id:%d", buff_obj->fb_id);

	map.handle = create.handle;
	ret = drmIoctl(m_Fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
	if (ret) {
	    LOGE("failed to map buf, %s", strerror(errno));
	    goto err;
	}

	buff_obj->vaddr = (uint8_t *)mmap(0, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, m_Fd, map.offset);
	if (!buff_obj->vaddr) {
	    LOGE("failed to mmap, %s", strerror(errno));
	    goto err;
	}
	memset(buff_obj->vaddr, 0, buff_obj->size);
	return buff_obj;
err:
	return NULL;
}

void DRMVideoSink::freeFB()
{
	LOGI("freeFB enter");
    int ret;
    struct drm_mode_destroy_dumb dd;

    for(int i = 0; i < DRM_BUFFER_COUNT; i++) {
	    if (m_pBufObj[i]->vaddr)
	        munmap(m_pBufObj[i]->vaddr, m_pBufObj[i]->size);

	    if (m_pBufObj[i]->fb_id) {
	        ret = drmModeRmFB(m_Fd, m_pBufObj[i]->fb_id);
	        if (ret)
	            LOGE("Failed to rmfb, %s\n", strerror(errno));
	    }

	    if (m_pBufObj[i]->handle) {
	        dd.handle = m_pBufObj[i]->handle;
	        ret = drmIoctl(m_Fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dd);
	        if (ret)
	            LOGE("Failed to destroy buffer, %s\n", strerror(errno));
	    }

	    if(m_pBufObj[i])
	    	free(m_pBufObj[i]);
	}
}

int DRMVideoSink::configureModeSetting()
{
	int ret;
	int i;
	drmModeModeInfo *mode;

	LOGI("configuring mode setting");

	m_Connector = drmModeGetConnector(m_Fd, m_ConnID);
	if (!m_Connector) {
		LOGE("drmModeGetConnector failed: %s (%d)", strerror(errno), errno);
		return -1;
	}

	//没有建立硬件显示pipeline时，选择和视频分辨率相同的mode建立显示pipeline
	for (i = 0; i < m_Connector->count_modes; i++) {
		if (m_Connector->modes[i].vdisplay == m_Height &&
			m_Connector->modes[i].hdisplay == m_Width) {
			mode = &m_Connector->modes[i];
			break;
		}
	}
	if (!mode) {
		LOGE("drmModeGetConnector failed: %s (%d)", strerror(errno), errno);
		return -1;
	}

	ret = drmModeSetCrtc (m_Fd, m_CrtcID, m_pBufObj[0]->fb_id, 0, 0,
					&m_ConnID, 1, mode);
	if (ret) {
		LOGE("drmModeSetCrtc failed: %s (%d)", strerror(errno), errno);
		return -1;
	}

	m_WDisplay = mode->hdisplay;
	m_HDisplay = mode->vdisplay;

	return 0;
}

int DRMVideoSink::findPlaneForCrtc()
{
	drmModePlane *plane;
	int i, pipe;

	plane = NULL;
	pipe = -1;
	for (i = 0; i < m_Res->count_crtcs; i++) {
		if (m_CrtcID == m_Res->crtcs[i]) {
			pipe = i;
			break;
		}
	}

	if (pipe == -1)
		return -1;

	for (i = 0; i < m_PlaneRes->count_planes; i++) {
		plane = drmModeGetPlane (m_Fd, m_PlaneRes->planes[i]);
		if (plane->possible_crtcs & (1 << pipe))
			  return plane->plane_id;
		drmModeFreePlane (plane);
	}

	return -1;
}

uint32_t DRMVideoSink::getDRMFormat(AVPixelFormat fmt)
{
	switch(fmt) {
		case AV_PIX_FMT_YUV420P:
			return DRM_FORMAT_YUV420;
		case AV_PIX_FMT_NV12:
			return DRM_FORMAT_NV12;
		case AV_PIX_FMT_NV21:
			return DRM_FORMAT_NV21;
		case AV_PIX_FMT_YUV422P:
			return DRM_FORMAT_YUV422;
		default: 
			LOGE("Unsupport format:%x", fmt);
			return 0;
	}
}

void DRMVideoSink::setDisplayArea(int x, int y, int w, int h)
{
	// LOGD("x:%d y:%d w:%d h:%d", x, y, w, h);
	m_DisplayX = x;
	m_DisplayY = y;
	m_DisplayW = w;
	m_DisplayH = h;

	//超出屏幕大小时错误处理
	if ((m_DisplayX + m_DisplayW) > m_WDisplay)
		m_DisplayW = m_WDisplay - m_DisplayX;

	if ((m_DisplayY + m_DisplayH) > m_HDisplay)
		m_DisplayH = m_HDisplay - m_DisplayY;
	LOGD("x:%d y:%d w:%d h:%d", m_DisplayX, m_DisplayY, m_DisplayW, m_DisplayH);
}

void DRMVideoSink::dumpPicture(const char *file, int start, int count)
{
	LOGI("dump file:%s start:%d count:%d", file, start, count);
	if(m_DumpIsStarted) {
		LOGW("dump is started!");
	}
	m_DumpFile = fopen(file, "w+");
	if(!m_DumpFile) {
		LOGE("open failed: %s",strerror(errno));
	}
	if(start < 0 || count < 0) {
		start = count = 0;
	}
	m_DumpStartCnt = start;
	m_DumpCount = count;
	if(m_DumpCount < 0)
		m_DumpCount = 0;
}

void DRMVideoSink::caculateRatio(int &result_x, int &result_y, int &result_w, int &result_h)
{
    double src_ratio, dst_ratio;

    src_ratio = (double) m_Width / m_Height;
    dst_ratio = (double) m_DisplayW / m_DisplayH;

    if (src_ratio > dst_ratio) {
		result_w = m_DisplayW;
		result_h = m_DisplayW / src_ratio;
		result_x = m_DisplayX;
		result_y = m_DisplayY + (m_DisplayH - result_h) / 2;
    } else if (src_ratio < dst_ratio) {
		result_w = m_DisplayH * src_ratio;
		result_h = m_DisplayH;
		result_x = m_DisplayX + (m_DisplayW - result_w) / 2;
		result_y = m_DisplayY;
    } else {
		result_x = m_DisplayX;
		result_y = m_DisplayY;
		result_w = m_DisplayW;
		result_h = m_DisplayH;
    }
}

void DRMVideoSink::setProperty(const char *pro, char *val)
{

}

void DRMVideoSink::syncHandler (int fd, unsigned int frame, unsigned int sec, unsigned int usec, bool *data)
{
  bool *waiting;

  waiting = data;
  *waiting = false;
}

bool DRMVideoSink::VSync(int fb_id)
{
	int ret;
	bool waiting, pageflip;
	drmEventContext evctxt;
	evctxt.version = DRM_EVENT_CONTEXT_VERSION;
	evctxt.page_flip_handler = syncHandler;
	evctxt.vblank_handler = syncHandler;

	ret = drmModePageFlip (m_Fd, m_CrtcID, fb_id,
	    DRM_MODE_PAGE_FLIP_EVENT, &waiting);
	if (ret)
	  goto pageflip_failed;

	while (waiting) {
		ret = drmHandleEvent (m_Fd, &evctxt);
		if (ret)
		  goto event_failed;
	}
	return true;

pageflip_failed:
	{
		LOGE("drmModePageFlip error: %s", strerror(errno));
		return false;
	}

event_failed:
	{
		LOGE("drmHandleEvent error: %s", strerror(errno));
		return false;
	}
}

int DRMVideoSink::showFrame(Frame *vframe)
{
	AVFrame *frame = vframe->frame;

	//使用两个buffer来显示，因为DRM在setPlane后，改变buffer内容显示就会变化，如果只用一个buffer，
	//在memcopy的中途刷新显示的话就会出现画面撕裂现象
	//偶数帧使用buffer1，奇数使用buffer0
	if(m_BuffIndex < 0 || m_BuffIndex >= DRM_BUFFER_COUNT) {
		m_BuffIndex = 0;
	}

	if(!m_pBufObj[m_BuffIndex]->vaddr || !frame->data[0]) {
		LOGE("addr(%p) or data(%p) error", m_pBufObj[m_BuffIndex]->vaddr, frame->data[0]);
		return -1;
	}

	if (m_DisplayW <= 0 || m_DisplayH <= 0 || m_DisplayX < 0 || m_DisplayY < 0) {
		LOGE("m_DisplayW(%d) m_DisplayH(%d) m_DisplayX(%d) m_DisplayY(%d) error", m_DisplayW, m_DisplayH, m_DisplayX, m_DisplayY);
		return -1;
	}

	//dumpPicture
	if(m_DumpCount && m_DumpFile && (m_ShowCnt == m_DumpStartCnt || m_DumpIsStarted)) {
		int ret = fwrite(frame->data[0], m_CopySize, 1, m_DumpFile);
		if(ret != 1) {
			LOGE("fwrite error: %s", strerror(errno));
		}
		m_DumpCount--;
		if(!m_DumpCount && m_DumpFile) {
			fclose(m_DumpFile);
			m_DumpFile = NULL;
			m_DumpIsStarted = false;
			m_ShowCnt = 0;
			m_DumpStartCnt = 0;
		}
		m_DumpIsStarted = true;
	}

	//如果需要保持视频比例，这里需要修改m_DisplayX, m_DisplayY, m_DisplayW, m_DisplayH
	int result_x, result_y, result_w, result_h;
	if(m_KeepRatio) {
		caculateRatio(result_x, result_y, result_w, result_h);
	}
	else {
		result_x = m_DisplayX;
		result_y = m_DisplayY;
		result_w = m_DisplayW;
		result_h = m_DisplayH;
	}

	//如果使用的是rk硬解，stride非空
	int *stride = (int *)frame->opaque;
	uint8_t *dst = m_pBufObj[m_BuffIndex]->vaddr;
	
	if(!stride) {
		//ffmpeg软解，将像素格式转换为YUV420SP
		if(frame->format == AV_PIX_FMT_YUV420P) {
			uint8_t* src_y = frame->data[0];
	        uint8_t* src_u = frame->data[1];
	        uint8_t* src_v = frame->data[2];
			//copy Y data
			memcpy(dst, src_y, frame->width * frame->height);
			
			memcpy(dst, frame->data[0], frame->width * frame->height);

			//copy UV data
			uint8_t* dst_uv = dst + frame->width * frame->height;
			int uv_size = (frame->width * frame->height) / 2;
	        for (int i = 0, j = 0; i < uv_size; i+=2,j++) {
	        	dst_uv[i] = src_u[j];
	        	dst_uv[i+1] = src_v[j];
	        }
		}
	}
	else {
		//ffmpeg硬解一般将yuv数据输出在一个buffer中，软解会由data[0] data[1] data[2]指定yuv数据
		//格式(pitchs, offset)等信息已经在调用drmModeAddFB2设置好，直接memcpy即可
		//frame->data[0]是硬解后由rga转换的像素,rk3328解码后固定格式NV12
		memcpy(dst, frame->data[0], m_CopySize);
	}

	/* crop the rect from framebuffer(m_Width, m_Height) to crtc(m_DisplayW, m_DisplayH) 
	   该缩放操作由硬件实现,rk3328中的VOP(video out processor)处理
	*/
	int ret = drmModeSetPlane(m_Fd, m_PlaneID, m_CrtcID, m_pBufObj[m_BuffIndex]->fb_id, 0,
			result_x, result_y, result_w, result_h,
			0, 0, m_Width << 16, m_Height << 16);
	if (ret) {
	    LOGE("drmModeSetPlane failed: %s (%d)", strerror(errno), errno);
	    return -1;
	}

	// if (!VSync(m_pBufObj[m_BuffIndex]->fb_id)) {
	// 	LOGE("sync error");
	// }

	m_BuffIndex++;
	m_ShowCnt++;
	// static int cnt = 0;
	// LOGD("frame display ok %d %lfS", cnt++, vframe->pts);

	return 0;
}